﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Threading;

using log4net;
using PriceLibrary.PriceService;
using PriceLibrary.Utilities;

namespace VDataAdapter.PriceServerAdapter
{
    // public delegate void PriceServiceConnectionError(VDataPSEventAdapterException ex);

    class VDataPSEventAdapterException : Exception
    {
        public VDataPSEventAdapterException(string msg, Exception inner) : base(msg, inner)
        {
        }
    }

    // this delegate handle case when exception happens
    delegate void PingExceptionHandler(Exception ex);

    /// <summary>
    /// This class is incharge of registering to Price Server
    /// It will pass PriceServer Event to the Event handler
    /// </summary>
    class VDataPSEventAdapter
    {
        private static ILog _log = LogManager.GetLogger(typeof(VDataPSEventAdapter));

        IPriceServerEventHandler _handler;

        PriceCallBack _priceCallBack;

        ChartCallBack _chartCallBack;

        private bool _listening;

        public VDataPSEventAdapter(IPriceServerEventHandler handler)
        {
            if(handler == null)
            {
                throw new VDataPSEventAdapterException("I can't work with Null handler", new NullReferenceException());
            }
            _handler = handler;
        }

        public void StartListening(string priceURI, string chartURI)
        {
            try
            {
                _listening = true;

                _priceCallBack = new PriceCallBack(priceURI);

                _chartCallBack = new ChartCallBack(chartURI);
                
                //Inits delegate for PriceClient                                
                _priceCallBack.EndReceivedNewTickers += NewTickersReceived;
                _priceCallBack.EndSendStockData += _handler.GetFirstTime;
                _priceCallBack.Top3PriceChanged += _handler.Top3PricesStock_Changed;
                _priceCallBack.PriceServer.GetTickersByFloorCode(100);
                                
                _priceCallBack.MarketInfoChanged += MarketInfoChanged;
                _priceCallBack.EndReceivedPtTopPrices += PutThroughChanged;
                _priceCallBack.EndReceivedPtTransactions += PutThroughTransactionChanged;
                ////_PriceCallBack.ServiceStatusChanged += new ChangeServiceStatus(ChangeServiceStatus);

                ////Gets tickers
                //_priceCallBack.PriceServer.GetTickersByFloorCode(100); // 100 get all
                //// will receive a new ticker event: in order to get list of stocks
                //_chartCallBack.ChartService.SubscribeSymbols(new List<string>());

                ////Transaction service!
                _chartCallBack.ChartService.SubscribeSymbols(new List<string>());
                _chartCallBack.EndReceivedAllTransaction += _handler.Transactions_Changed;
            }
            catch (CommunicationObjectFaultedException ex)
            {
                _log.Fatal("Cannot init communication");
                _handler.ConnectionErrorHandler(ex);
            }
            try
            {
                Ping(PingExceptionHandlerFunction);
            }
            catch (Exception ex)
            {
                _log.Fatal("Error start pinging thread");
                UnRegisterPriceService();
                _handler.ConnectionErrorHandler(ex);
            }
        }

        public void UnRegisterPriceService()
        {
            _priceCallBack.EndSendStockData -= _handler.GetFirstTime;
            _priceCallBack.Top3PriceChanged -= _handler.Top3PricesStock_Changed;
            
            _priceCallBack.EndReceivedNewTickers -= NewTickersReceived;

            _priceCallBack.MarketInfoChanged -= MarketInfoChanged;
            _priceCallBack.EndReceivedPtTopPrices -= PutThroughChanged;
            _priceCallBack.EndReceivedPtTransactions -= PutThroughTransactionChanged;

            _chartCallBack.EndReceivedAllTransaction -= _handler.Transactions_Changed;

            // _listening = false;

            // _pingThread.Join();
        }


        Thread _pingThread;
        private void PingExceptionHandlerFunction(Exception ex)
        {
            _log.Fatal("Error connection when ping");
            UnRegisterPriceService();
            _handler.ConnectionErrorHandler(ex);
        }

        private void Ping(PingExceptionHandler handler)
        {            
            _pingThread = new Thread(() => DoPing(handler));
            _pingThread.Start();
        }

        /// <summary>
        /// Pinging for reconnection if price server is down
        /// </summary>
        private void DoPing(PingExceptionHandler handler)
        {
            while (_listening)
            {
                try
                {                    
                    if (((ICommunicationObject)_priceCallBack.PriceServer).State == CommunicationState.Faulted)
                    {
                        Thread.Sleep(5000);
                        _log.ErrorFormat("CommunicationState.Faulted State at {0}", DateTime.Now.ToString());
                        handler(new VDataPSEventAdapterException("Error connecting to Price Server", null));
                        // throw new VDataPSEventAdapterException("Error connecting to Price Server", null);
                        // _priceCallBack.PriceServer = _ClientManager.CreatePriceServer(_PriceCallBack);
                    }
                    _priceCallBack.PriceServer.Ping();
                    //_priceCallBack.PriceServer.SubscribeNewSymbols(new List<string>());
                }
                catch (Exception ex)
                {
                    _log.Error("Error PriceCallBackPing", ex);
                    _listening = false;
                    handler(new VDataPSEventAdapterException("Error pinging to Price Server", ex));
                    // throw new VDataPSEventAdapterException("Error pinging to Price Server", ex);
                }
            }
        }

        private void NewTickersReceived(IList<string> newtickers)
        {
            _log.Info("New Tickers Received: " + newtickers.Count + " tickers");
            try
            {
                if (_priceCallBack != null)
                {

                    foreach (string newticker in newtickers)
                    {
                        _priceCallBack.Tickers.Add(newticker);
                    }

                    //Đăng ký lên server!
                    _log.Info("PriceCallBack Register to PriceServer");
                    _priceCallBack.PriceServer.SubscribeNewSymbols(newtickers as List<string>);

                    newtickers = AddMarketTickers(newtickers);
                    //Dang ky Transactions Service
                    _log.Info("ChartCallBack Register to PriceServer");
                    _chartCallBack.ChartService.SubscribeAllSymbols(newtickers as List<string>);
                }
            }
            catch (Exception ex)
            {
                _log.Error("Error New Tickers Received: " + ex.ToString());
                _handler.ConnectionErrorHandler(new VDataPSEventAdapterException("Error while process new tickers received event", ex));
            }
        }

        /// <summary>
        /// Add symbols of market to newtickers
        /// </summary>
        /// <param name="newtickers"></param>
        /// <returns></returns>
        private IList<string> AddMarketTickers(IList<string> newtickers)
        {
            if (!newtickers.Contains("HOSE"))
                newtickers.Add("HOSE");
            if (!newtickers.Contains("HNX"))
                newtickers.Add("HNX");
            if (!newtickers.Contains("UPCOM"))
                newtickers.Add("UPCOM");
            if (!newtickers.Contains("VN30"))
                newtickers.Add("VN30");
            if (!newtickers.Contains("HNX30"))
                newtickers.Add("HNX30");

            return newtickers;
        }
         
        private void MarketInfoChanged(IDictionary<int, StockBase> marketInfos)
        {
            var d = new Dictionary<int, MarketInfo>();
            foreach (KeyValuePair<int, StockBase> key in marketInfos)
            {
                d.Add(key.Key, (MarketInfo)key.Value);
            }
            _handler.MarketInfo_Changed(d);
        }

        private void PutThroughChanged(IList<StockBase> newstockbase)
        {
            if (newstockbase.Count > 0)
            {
                var list = new List<StockInfoDyns>();
                foreach (StockBase s in newstockbase)
                {
                    list.Add((StockInfoDyns)s);
                }
                _handler.PutThroughTopPrices_Changed(list);
            }
        }

        private void PutThroughTransactionChanged(IList<StockBase> transactions)
        {
            var list = new List<Transaction>();
            foreach (StockBase s in transactions)
            {
                list.Add((Transaction)s);
            }
            _handler.PutThroughTransactions_Changed(list);
        }

        
    }
}
